package com.fortune.conf;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fortune.entity.OperationLogEntity;
import com.fortune.mapper.OperationLogMapper;
import com.fortune.service.OperationLogService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 类 {@code WebLogAspect} 所有web请求响应访问记录切面 <br> 用于记录所有web请求响应的访问记录与结束记录.
 */
@Aspect
@Component
@Slf4j
public class OperationLogWebLogAspect {
    @Autowired
    private OperationLogMapper operationLogMapper;
    //初始日志记录实体
    OperationLogEntity operationLogEntity = new OperationLogEntity();

    /**
     * 声明一个线程，用于记录请求与响应整个周期期间在服务端消耗的时间
     */
//    private final ThreadLocal<Long> startTime = new ThreadLocal<>();
    /**
     * 访问localhost时打印的IP地址
     */
    private static final String IP_LOCALHOST ="127.0.0.1";
    @Pointcut("execution(* com.*.controller..*.*(..))")
    public void webLog(){
    }
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 执行时间：在请求响应之前，即请求到达当前服务端
     * @param joinPoint 切入点
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint){
            log.debug("  ---------- Start WebLogAspect doBefore --------------");
            //记录请求开始时间
            operationLogEntity.setCurrentTimeMillis(System.currentTimeMillis());
            //创建时间
            operationLogEntity.setCreateTime(LocalDateTime.now());
            //存储当前请求的行为操作，需要其已增加 @OperationLog 注解配置的情况下，该位置方能获取到相应的行为操作，否则为空
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            OperationLog logObj = method.getAnnotation(OperationLog.class);

            //方法描述
            String logObjDescribe = "";
            if (logObj != null) {
                logObjDescribe = logObj.describe();
            }
            //日志描述
            operationLogEntity.setFuncDescribe(logObjDescribe);

            //获取请求对象，为下面处理逻辑提供request对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            //在系统启动时拦截器经过该位置时会触发空指针异常，这里需要进行非空判断
            if (attributes != null && joinPoint != null) {
                HttpServletRequest request = attributes.getRequest();
                String urlStr = request.getRequestURL().toString();
                String remoteAddr = getIpAddr(request);
                //包名
                String packageName = joinPoint.getSignature().getDeclaringTypeName();
                String[] splits = packageName.split("\\.");
                String sysCode = splits.length >= 2 ? splits[1] : splits.toString();
                String requestType = request.getMethod();
                Object[] args = joinPoint.getArgs();

//            operationLogEntity.setReqParameter(JSON.toJSONString(request.getParameterMap()));
                operationLogEntity.setReqParameter(getFormData(args,request,null));
                operationLogEntity.setAddr(urlStr);operationLogEntity.setIp(remoteAddr);operationLogEntity.setMethod(requestType);
                operationLogEntity.setSyscode(sysCode);
            }
            log.debug(" ---------- End WebLogAspect doBefore --------------");

    }

    /**
     *
     * @TODO 进行记录请求访问ip、请求当前登录人id、请求地址、响应结果
     * 执行时间：在请求响应之后，即请求已经经过contrroller处理返回后
     * @param ret 参数
     */
    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //存储响应日志
                operationLogEntity.setResContent(JSONObject.toJSONString(ret));
                //存储请求历时
                int l = (int)(System.currentTimeMillis() - operationLogEntity.getCurrentTimeMillis());
                operationLogEntity.setDuration(l);
                startTime.remove();
                operationLogMapper.insert(operationLogEntity);
            }
        }).start();

    }

    /**
     * 异常切入
     * @param error 异常
     */
    @AfterThrowing(pointcut = "webLog()",throwing = "error")
    public void doAfterThrowing(Throwable error){
    }

    /**
     * 获取请求ip地址
     * @param request 请求
     * @return IP
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        String unknown = "unknown";
        if(ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip == null){
            ip = IP_LOCALHOST;
        }
        return ip;
    }

    /**
     * * 获取POST GET的data数据 * @param request * @return大
     */
    private static String getFormData(Object[] args, HttpServletRequest request, Object requestBody) {
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            if (existFileUpload(request)) {
                Map<String, Object> mapData = new HashMap<>();
                request.getParameterMap().entrySet().stream().forEach(entry -> {
                    mapData.put(entry.getKey(), entry.getValue());
                });
                return JSONObject.toJSONString(mapData);
            } else {
                if (requestBody == null) {
                    if (args != null && args.length > 0) {
                        List<Object> newArgs = new ArrayList<>();
                        for (Object arg : args) {
                            if (arg instanceof HttpServletRequest) {

                            } else if (arg instanceof BindingResult) {

                            } else {
                                newArgs.add(arg);
                            }
                        }
                        return JSONObject.toJSONString(newArgs);
                    }
                } else if (requestBody instanceof BeanPropertyBindingResult) {
                    return JSONObject.toJSONString(((BeanPropertyBindingResult) requestBody).getTarget());
                } else if (requestBody instanceof MultipartHttpServletRequest) {
                    Enumeration<String> parameterNames = request.getParameterNames();
                    Map<String, String> map = new HashMap<>();
                    while (parameterNames.hasMoreElements()) {
                        String paramName = (String) parameterNames.nextElement();
                        String paramValue = request.getParameter(paramName);
                        map.put(paramName, paramValue);
                    }
                    return JSONObject.toJSONString(map);
                } else {
                    return JSONObject.toJSONString(requestBody);
                }
            }
        }

        String data = request.getParameterMap().entrySet().stream().map(entry -> {
            String result;
            String[] value = entry.getValue();
            if (value != null && value.length > 1) {
                result = Arrays.stream(value).map(s -> entry.getKey() + "=" + s).collect(Collectors.joining("&"));
            } else {
                result = entry.getKey() + "=" + value[0];
            }
            return result;
        }).collect(Collectors.joining("&"));
        return data;
    }
    /***是否是上传文件* @param request* @return*/
    static boolean existFileUpload(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (contentType != null && (contentType.startsWith("multipart/") || contentType.contains("boundary="))) {
            return true; // 是上传文件的诗求

        } else {
            return false; // 不是上传文件的请求
        }

    }

}
